/*
 * This file is part of git-as-svn. It is subject to the license terms
 * in the LICENSE file found in the top-level directory of this distribution
 * and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn,
 * including this file, may be copied, modified, propagated, or distributed
 * except according to the terms contained in the LICENSE file.
 */
package svnserver.repository.git.prop;

import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.attributes.AttributesRule;
import org.eclipse.jgit.errors.InvalidPatternException;
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import svnserver.Loggers;
import svnserver.repository.git.path.Wildcard;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.PatternSyntaxException;

/**
 * Factory for properties, generated by .gitattributes.
 *
 * @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
 */
public final class GitAttributesFactory implements GitPropertyFactory {
  @NotNull
  private static final Logger log = Loggers.git;
  @NotNull
  private static final Attribute[] emptyAttributes = {};

  @NotNull
  @Override
  public String getFileName() {
    return ".gitattributes";
  }

  @NotNull
  @Override
  public GitProperty[] create(@NotNull InputStream stream) throws IOException {
    AttributesNode r = new AttributesNode();
    r.parse(stream);
    final List<GitProperty> properties = new ArrayList<>();
    for (AttributesRule rule : r.getRules()) {
      final Wildcard wildcard;
      try {
        wildcard = new Wildcard(rule.getPattern());
      } catch (InvalidPatternException | PatternSyntaxException e) {
        log.warn("Found invalid git pattern: {}", rule.getPattern());
        continue;
      }

      final Attributes attrs = new Attributes(rule.getAttributes().toArray(emptyAttributes));

      final EolType eolType = getEolType(attrs);
      if (eolType != null) {
        processProperty(properties, wildcard, SVNProperty.MIME_TYPE, eolType.mimeType);
        processProperty(properties, wildcard, SVNProperty.EOL_STYLE, eolType.eolStyle);
      }
      processProperty(properties, wildcard, SVNProperty.NEEDS_LOCK, getNeedsLock(attrs));

      final String filter = getFilter(attrs);
      if (filter != null)
        properties.add(new GitFilterProperty(wildcard.getMatcher(), filter));
    }

    return properties.toArray(GitProperty.emptyArray);
  }

  private static void processProperty(@NotNull List<GitProperty> properties, @NotNull Wildcard wildcard, @NotNull String property, @Nullable String value) {
    if (value == null) {
      return;
    }
    if (!value.isEmpty()) {
      if (wildcard.isSvnCompatible()) {
        properties.add(new GitAutoProperty(wildcard.getMatcher(), property, value));
      }
      properties.add(new GitFileProperty(wildcard.getMatcher(), property, value));
    } else {
      properties.add(new GitFileProperty(wildcard.getMatcher(), property, null));
    }
  }

  /**
   * @see EolStreamTypeUtil
   */
  @Nullable
  private GitAttributesFactory.EolType getEolType(@NotNull Attributes attrs) {
    if (attrs.isSet("binary") || attrs.isUnset("text"))
      return EolType.Binary;

    final String eol = attrs.getValue("eol");
    if (eol != null) {
      switch (eol) {
        case "lf":
          return EolType.LF;
        case "crlf":
          return EolType.CRLF;
      }
    }

    if (attrs.isUnspecified("text"))
      return null;

    return EolType.Native;
  }

  private enum EolType {
    Binary(SVNFileUtil.BINARY_MIME_TYPE, ""),
    Native("", SVNProperty.EOL_STYLE_NATIVE),
    LF("", SVNProperty.EOL_STYLE_LF),
    CRLF("", SVNProperty.EOL_STYLE_CRLF);

    @NotNull
    private final String mimeType;
    @NotNull
    private final String eolStyle;

    EolType(@NotNull String mimeType, @NotNull String eolStyle) {
      this.mimeType = mimeType;
      this.eolStyle = eolStyle;
    }
  }

  @Nullable
  private String getNeedsLock(@NotNull Attributes attrs) {
    if (attrs.isSet("lockable"))
      return "*";

    return null;
  }

  @Nullable
  private static String getFilter(@NotNull Attributes attrs) {
    return attrs.getValue("filter");
  }
}
